﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Text;

using System.Xml;
using System.Xml.Linq;
using System.CodeDom;
using System.Xml.Serialization;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Collections;

namespace Stefan.CodeDomGen
{
    public class ProxyGenerator
    {
        #region inner types

        private class TypeData
        {
            public string ProxyName { get; set; }
            public Type UnderlyingType { get; set; }
            public CodeTypeDeclaration TypeDeclaration { get; set; }

            public List<MemberData> Members { get; set; }
        }

        private class MemberData
        {
            public string VarName { get; set; }
            public MemberInfo MemberInfo { get; set; }

            public MethodInfo GetMethod { get; set; }
            public MethodInfo SetMethod { get; set; }

            public string MemberInfoVarName { get; set; }
            public string MemberInfoVarName2 { get; set; }

            public bool IsProperty { get { return GetMethod != null || SetMethod != null; } }
        }

        #endregion

        #region constants

        private const string UNDERLYING_INSTANCE_VARNAME = "_underlyingInstance";
        private const string UNDERLYING_INSTANCE_PROPNAME = "UnderlyingInstance";

        private const string FROMINSTANCE_METHODNAME = "FromUnderlyingInstance";
        private const string CREATEINSTANCE_METHODNAME = "CreateUnderlyingInstance";

        private const string MEMBERINFO_VARNAME = "_memberInfo";

        private static readonly HashSet<string> ObjectMethods = new HashSet<string>() { "MemberwiseClone", "ToString", "Equals", "GetHashCode", "GetType" };

        #endregion

        #region variables

        private Dictionary<Type, TypeData> inputTypes;
        private CodeCompileUnit _unit;
        private CodeNamespace _ns;
        private string fileName;

        private bool _getNonInheritedMembersOnly = true;

        #endregion

        #region public

        public void GenerateClassProxy(string xml)
        {
            InitFromXml(xml);
            CreateProxies();
            SaveFile();
        }

        #endregion

        #region private xml parsing

        private void InitFromXml(string xml)
        {
            inputTypes = new Dictionary<Type,TypeData>();
            XDocument doc = XDocument.Parse(xml);

            this.fileName = doc.Root.Attribute("ProxyCodeFile").Value;

            this._unit = new CodeCompileUnit();
            this._ns = new CodeNamespace(doc.Root.Attribute("ProxyNamespace").Value);
            this._unit.Namespaces.Add(_ns);

            foreach (XElement el in doc.Root.Descendants().Where(el => el.Name == "class"))
            {
                string typeName = el.Attribute("Name").Value + "," + el.Parent.Attribute("Name").Value;
                Type type = Type.GetType(typeName);
                if (type == null) throw new ApplicationException ("Type not found: " + typeName);

                string proxyName = el.Attribute("ProxyName").Value;
                if (string.IsNullOrEmpty (proxyName)) proxyName = type.Name + "Proxy";
                inputTypes[type] = new TypeData() { ProxyName = proxyName, UnderlyingType = type };
            }
        }

        #endregion

        #region private

        private void CreateProxies()
        {
            // 1st loop - we want to have all CodeTypeDeclaration in the first pass for later references
            foreach (KeyValuePair<Type, TypeData> pair in this.inputTypes)
            {
                TypeData typeData = pair.Value;

                CodeTypeDeclaration cl = new CodeTypeDeclaration(typeData.ProxyName);
                cl.IsClass = true;
                cl.Attributes = MemberAttributes.Public;
                cl.BaseTypes.Add(typeof(object));
                this._ns.Types.Add(cl);

                Type underlyingType = typeData.UnderlyingType.IsVisible ? typeData.UnderlyingType : typeof(object);
                this.AddField(cl, underlyingType, UNDERLYING_INSTANCE_VARNAME).Attributes = MemberAttributes.Public;
                this.AddProperty(cl, underlyingType, UNDERLYING_INSTANCE_PROPNAME, UNDERLYING_INSTANCE_VARNAME);

                // update the TypeData with the new CodeTypeDeclaration
                typeData.TypeDeclaration = cl;
                // init the members
                InitializeMemberDatas(typeData);
            }

            // 2nd pass - put stuff in the type declarations
            foreach (KeyValuePair<Type, TypeData> pair in this.inputTypes)
            {
                TypeData typeData = pair.Value;
                CodeTypeDeclaration cl = typeData.TypeDeclaration;

                CreateMemberInfoVariables(typeData);

                CreateStaticConstructor(typeData);
                CreateFromInstanceMethod(typeData);
                CreateConversionOperator(typeData);

                CreateWrappers(typeData);
            }
        }

        private void CreateStaticConstructor(TypeData typeData)
        {
            CodeTypeConstructor c = new CodeTypeConstructor();
            typeData.TypeDeclaration.Members.Add(c);

            c.Statements.Add (new CodeVariableDeclarationStatement(new CodeTypeReference(typeof(Type)), "_underlyingType",
                new CodeMethodInvokeExpression(
                    new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(typeof(Type)), "GetType"),
                    new CodePrimitiveExpression (typeData.UnderlyingType.AssemblyQualifiedName))));

            // creates
            //System.Collections.Generic.Dictionary<string, System.Reflection.MemberInfo> dic;
            c.Statements.Add(new CodeVariableDeclarationStatement(new CodeTypeReference(typeof(Dictionary<string, MemberInfo>)), "dic"));

            // creates
            //dic = new System.Collections.Generic.Dictionary<string, System.Reflection.MemberInfo>();
            c.Statements.Add(new CodeAssignStatement(
                new CodeVariableReferenceExpression("dic"),
                new CodeObjectCreateExpression(new CodeTypeReference(typeof(Dictionary<string, MemberInfo>)), new CodeExpression[] { })));

            Dictionary<string, string> dic = new Dictionary<string, string>();

            //creates
            //List<MemberInfo> members = new List<MemberInfo>();
            c.Statements.Add(new CodeVariableDeclarationStatement(new CodeTypeReference(typeof(List<MemberInfo>)), "members"));
            c.Statements.Add(new CodeAssignStatement(
                new CodeVariableReferenceExpression("members"),
                new CodeObjectCreateExpression(new CodeTypeReference(typeof(List<MemberInfo>)), new CodeExpression[] { })));

            /*
            members.AddRange(_underlyingType.GetMembers(BindingFlags.Instance | BindingFlags.NonPublic));
            members.AddRange(_underlyingType.GetMembers(BindingFlags.Instance | BindingFlags.Public));
            members.AddRange(_underlyingType.GetMembers(BindingFlags.Static | BindingFlags.NonPublic));
            members.AddRange(_underlyingType.GetMembers(BindingFlags.Static | BindingFlags.Public));
            */
            c.Statements.Add(new CodeMethodInvokeExpression(
                new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("members"), "AddRange"),
                new CodeMethodInvokeExpression(
                    new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("_underlyingType"), "GetMembers"),
                    new CodeBinaryOperatorExpression(
                        new CodeFieldReferenceExpression (new CodeTypeReferenceExpression(typeof(BindingFlags)), "Instance"),
                        CodeBinaryOperatorType.BitwiseOr, 
                        new CodeFieldReferenceExpression (new CodeTypeReferenceExpression(typeof(BindingFlags)), "NonPublic"))
                    )));

            c.Statements.Add(new CodeMethodInvokeExpression(
                new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("members"), "AddRange"),
                new CodeMethodInvokeExpression(
                    new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("_underlyingType"), "GetMembers"),
                    new CodeBinaryOperatorExpression(
                        new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(BindingFlags)), "Instance"),
                        CodeBinaryOperatorType.BitwiseOr,
                        new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(BindingFlags)), "Public"))
                    )));

            c.Statements.Add(new CodeMethodInvokeExpression(
                new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("members"), "AddRange"),
                new CodeMethodInvokeExpression(
                    new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("_underlyingType"), "GetMembers"),
                    new CodeBinaryOperatorExpression(
                        new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(BindingFlags)), "Static"),
                        CodeBinaryOperatorType.BitwiseOr,
                        new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(BindingFlags)), "Public"))
                    )));

            c.Statements.Add(new CodeMethodInvokeExpression(
                new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("members"), "AddRange"),
                new CodeMethodInvokeExpression(
                    new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("_underlyingType"), "GetMembers"),
                    new CodeBinaryOperatorExpression(
                        new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(BindingFlags)), "Static"),
                        CodeBinaryOperatorType.BitwiseOr,
                        new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(BindingFlags)), "NonPublic"))
                    )));

            CodeIterationStatement loop = 
            new CodeIterationStatement(
                            new CodeVariableDeclarationStatement(typeof(int), "i", new CodePrimitiveExpression(0)),
                            new CodeBinaryOperatorExpression (new CodeVariableReferenceExpression("i"), 
                                                CodeBinaryOperatorType.LessThan,
                                                new CodeFieldReferenceExpression(new CodeVariableReferenceExpression("members"), "Count")), 
                            new CodeAssignStatement (
                                new CodeVariableReferenceExpression("i"),
                                new CodeBinaryOperatorExpression(
                                    new CodeVariableReferenceExpression("i"), 
                                    CodeBinaryOperatorType.Add, 
                                    new CodePrimitiveExpression(1))),
                                    
                                    new CodeVariableDeclarationStatement (typeof(MemberInfo), "inf", GetIndexerRef("members", GetVar("i")))

                                    
                                    );

            CodeStatement dicAdd = new CodeAssignStatement(
                        GetIndexerRef("dic", GetMethodInvoke("inf", "ToString")), GetVar("inf")
                                        );

            if (this._getNonInheritedMembersOnly)
            {
                loop.Statements.Add(
                    new CodeConditionStatement (
                        new CodeBinaryOperatorExpression(GetPropRef("inf", "DeclaringType"), CodeBinaryOperatorType.IdentityEquality, GetVar("_underlyingType")),
                        dicAdd)
                    );
            }
            else loop.Statements.Add(dicAdd);

            c.Statements.Add(loop);

            foreach (MemberData memberData in typeData.Members)
            {
                if (memberData.IsProperty)
                {
                    if (memberData.GetMethod != null) c.Statements.Add (GetInitStaticFieldStatement(typeData, typeof(MethodInfo), memberData.MemberInfoVarName, "dic", memberData.GetMethod.ToString()));
                    if (memberData.SetMethod != null) c.Statements.Add (GetInitStaticFieldStatement(typeData, typeof(MethodInfo), memberData.MemberInfoVarName2, "dic", memberData.SetMethod.ToString()));
                }
                else if (memberData.MemberInfo is ConstructorInfo)
                {
                    c.Statements.Add(GetInitStaticFieldStatement(typeData, typeof(ConstructorInfo), memberData.MemberInfoVarName, "dic", memberData.MemberInfo.ToString()));
                }
                else
                {
                    c.Statements.Add (GetInitStaticFieldStatement(typeData, typeof(MethodInfo), memberData.MemberInfoVarName, "dic", memberData.MemberInfo.ToString()));
                }
            }
        }

        private CodeStatement GetInitStaticFieldStatement(TypeData typeData, Type memberType, string fieldName, string dicName, string indexValue)
        {
            return
                new CodeAssignStatement(
                    GetStatFieldRef (typeData.TypeDeclaration.Name, fieldName),
                    new CodeCastExpression(memberType, GetIndexerRef(dicName, new CodePrimitiveExpression(indexValue)))
                );
        }

        private void CreateConversionOperator(TypeData typeData)
        {
            // nice try but won't be translated as implicit operator XXX - should use code snippet here

            // create the operator only for visible types
            if (!typeData.UnderlyingType.IsVisible) return;

            CodeMemberMethod m = AddMethodFromMethodInfo(typeData.TypeDeclaration, FromUnderlyingInstance_MI);
            m.Name = "op_Implicit";

            // need the IL specialname attribute here to work
            m.Attributes |= MemberAttributes.Overloaded;

            m.Parameters[0].Type = new CodeTypeReference(typeData.UnderlyingType);
            m.ReturnType = new CodeTypeReference(typeData.TypeDeclaration.Name);

            m.Statements.Add(
                    new CodeMethodReturnStatement(
                            new CodeMethodInvokeExpression (
                                new CodeMethodReferenceExpression (null, FROMINSTANCE_METHODNAME),
                            new CodeVariableReferenceExpression("instance"))
                        )
                );
        }

        private void CreateFromInstanceMethod(TypeData typeData)
        {
            CodeMemberMethod m = AddMethodFromMethodInfo(typeData.TypeDeclaration, FromUnderlyingInstance_MI);
            if (typeData.UnderlyingType.IsVisible)
            {
                m.Parameters[0].Type = new CodeTypeReference(typeData.UnderlyingType);
            }

            m.Statements.Add(new CodeVariableDeclarationStatement(new CodeTypeReference(typeData.TypeDeclaration.Name), "obj"));

            m.Statements.Add (new CodeAssignStatement(
                new CodeVariableReferenceExpression ("obj"),
                new CodeObjectCreateExpression( new CodeTypeReference(typeData.TypeDeclaration.Name), new CodeExpression[]{})));

            m.Statements.Add(new CodeAssignStatement
                (new CodeFieldReferenceExpression
                    (new CodeVariableReferenceExpression("obj"), UNDERLYING_INSTANCE_VARNAME), new CodeVariableReferenceExpression("instance")));

            m.Statements.Add (new CodeMethodReturnStatement(new CodeVariableReferenceExpression("obj")));

            m.ReturnType = new CodeTypeReference(typeData.TypeDeclaration.Name);
        }

        private void CreateMemberInfoVariables(TypeData typeData)
        {
            // support for constructors, methods & properties for now
            int globalCounter = 0;
            foreach (MemberData memberData in typeData.Members)
            {
                MemberInfo info = memberData.MemberInfo;
                if (info is PropertyInfo)
                {
                    if (memberData.GetMethod != null)
                    {
                        memberData.MemberInfoVarName = GetMemberInfoVariableName(ref globalCounter);
                        AddStaticField(typeData.TypeDeclaration, typeof(MethodInfo), memberData.MemberInfoVarName);
                    }
                    if (memberData.SetMethod != null)
                    {
                        memberData.MemberInfoVarName2 = GetMemberInfoVariableName(ref globalCounter);
                        AddStaticField(typeData.TypeDeclaration, typeof(MethodInfo), memberData.MemberInfoVarName2);
                    }
                }
                else if (info is MethodInfo)
                {
                    memberData.MemberInfoVarName = GetMemberInfoVariableName(ref globalCounter);
                    AddStaticField(typeData.TypeDeclaration, typeof(MethodInfo), memberData.MemberInfoVarName);
                }
                else if (info is ConstructorInfo)
                {
                    memberData.MemberInfoVarName = GetMemberInfoVariableName(ref globalCounter);
                    AddStaticField(typeData.TypeDeclaration, typeof(ConstructorInfo), memberData.MemberInfoVarName);
                }
            }
        }

        private string GetMemberInfoVariableName(ref int counter) { return MEMBERINFO_VARNAME + counter++; }

        private void InitializeMemberDatas(TypeData typeData)
        {
            Type underlyingType = typeData.UnderlyingType;
            bool typeVisible = underlyingType.IsVisible;

            // TODO - here is the place to filter what types of members are to be extracted
            List<MemberInfo> members = new List<MemberInfo>();
            members.AddRange(underlyingType.GetMembers(BindingFlags.Instance | BindingFlags.NonPublic));
            members.AddRange(underlyingType.GetMembers(BindingFlags.Instance | BindingFlags.Public));
            members.AddRange(underlyingType.GetMembers(BindingFlags.Static | BindingFlags.NonPublic));
            members.AddRange(underlyingType.GetMembers(BindingFlags.Static | BindingFlags.Public));

            // we don't want the static constructor and also the method infos that are the getters and setters for properties or destructors
            typeData.Members = members.Where(m => !this._getNonInheritedMembersOnly || m.DeclaringType == underlyingType).Where(m => m is PropertyInfo || (m is MethodInfo && !IsMethodInterfaceExplicit((MethodInfo)m) && !IsPropertyMethodInfo((MethodInfo)m) && !IsDestructor((MethodInfo)m)) || (m is ConstructorInfo && !((ConstructorInfo)m).IsStatic))
                .Select(m => new MemberData() { MemberInfo = m }).ToList();

            // init the setter/getter methods for properties
            foreach (MemberData data in typeData.Members)
            {
                PropertyInfo prop = data.MemberInfo as PropertyInfo;
                if (prop != null)
                {
                    // try the public getter first, if null, go for the non-public
                    data.GetMethod = prop.GetGetMethod();
                    if (data.GetMethod == null) data.GetMethod = prop.GetGetMethod(true);

                    // try the public setter first, if null, go for the non-public
                    data.SetMethod = prop.GetSetMethod();
                    if (data.SetMethod == null) data.SetMethod = prop.GetSetMethod(true);

                }
            }
        }

        private void CreateWrappers(TypeData typeData)
        {
            foreach (MemberData memberData in typeData.Members)
            {
                if (memberData.MemberInfo is ConstructorInfo) AddConstructorWrapper(typeData, memberData);
                else if (memberData.MemberInfo is MethodInfo) AddMethodWrapper(typeData, memberData);
                else if (memberData.MemberInfo is PropertyInfo) AddPropertyWrapper(typeData, memberData);
            }
        }

        #endregion

        #region code helpers

        private CodeMemberProperty AddPropertyWrapper(TypeData typeData, MemberData memberData)
        {
            CodeMemberProperty p = new CodeMemberProperty();
            PropertyInfo pi = (PropertyInfo)memberData.MemberInfo;

            p.Name = memberData.MemberInfo.Name;
            p.Type = this.GetVisibleOrProxyTypeReference(pi.PropertyType);
            p.Attributes = MemberAttributes.Public | MemberAttributes.Final;

            CodeMemberMethod dummy = null, dummy2 = null;
            ParameterInfo[] parameters = null, parameters2 = null;
            bool isStatic = false;

            if (memberData.SetMethod != null)
            {
                parameters2 = memberData.SetMethod.GetParameters();
                // skip the 'value' parameter in the setter, it is in 1st place
                parameters = parameters2.Take(parameters2.Length - 1).ToArray();
                dummy2 = this.GetMethodFromParameterInfos("dummy", parameters2);
                isStatic = memberData.SetMethod.IsStatic;
            }
            else if (memberData.GetMethod != null) { parameters = memberData.GetMethod.GetParameters(); isStatic = memberData.GetMethod.IsStatic; }

            if (parameters == null) return null;
            if (isStatic) p.Attributes |= MemberAttributes.Static;
            dummy = this.GetMethodFromParameterInfos("dummy", parameters);

            p.Parameters.AddRange(dummy.Parameters);
            if (memberData.GetMethod != null) p.GetStatements.AddRange(GetMethodWrapperStatements(typeData, parameters, p.Parameters, pi.PropertyType, memberData.MemberInfoVarName, isStatic, false));
            if (memberData.SetMethod != null) p.SetStatements.AddRange(GetMethodWrapperStatements(typeData, parameters2, dummy2.Parameters, typeof(void), memberData.MemberInfoVarName2, isStatic, false));

            typeData.TypeDeclaration.Members.Add(p);
            return p;
        }

        private CodeMemberMethod AddConstructorWrapper(TypeData typeData, MemberData memberData)
        {
            ConstructorInfo ci = (ConstructorInfo)memberData.MemberInfo;
            CodeMemberMethod m = AddMethodFromParameterInfos (typeData.TypeDeclaration, CREATEINSTANCE_METHODNAME, ci.GetParameters());
            m.Attributes |= MemberAttributes.Static;
            m.ReturnType = new CodeTypeReference(typeData.TypeDeclaration.Name);

            m.Statements.AddRange(GetMethodWrapperStatements(typeData, ci.GetParameters(), m.Parameters, typeof(void), memberData.MemberInfoVarName, false, true));
            m.Statements.Add(
                    new CodeMethodReturnStatement(
                                GetStaticMethodInvoke (new CodeTypeReference(typeData.TypeDeclaration.Name), FROMINSTANCE_METHODNAME,
                                new CodeCastExpression(GetVisibleTypeReference(typeData.UnderlyingType), GetVar("ret"))
                            ))
                );
            
            return m;
        }

        private CodeMemberMethod AddMethodWrapper(TypeData typeData, MemberData memberData)
        {
            MethodInfo mi = (MethodInfo)memberData.MemberInfo;
            CodeMemberMethod m = AddMethodFromMethodInfo(typeData.TypeDeclaration, mi);
            if (ObjectMethods.Contains(m.Name)) m.Attributes |= MemberAttributes.New;

            m.Statements.AddRange(GetMethodWrapperStatements(typeData, mi.GetParameters(), m.Parameters, mi.ReturnType, memberData.MemberInfoVarName, mi.IsStatic, false));
            return m;
        }

        private CodeStatementCollection GetMethodWrapperStatements(TypeData typeData, ParameterInfo[] parameters, CodeParameterDeclarationExpressionCollection mParameters, Type returnType, string staticVarName, bool isStatic, bool isConstructor)
        {
            CodeStatementCollection coll = new CodeStatementCollection();

            coll.Add(
                    DeclareVar("objs", typeof(object[]), new CodeArrayCreateExpression(typeof(object), parameters.Length))
                );

            CodeStatementCollection beforeInvokeColl = new CodeStatementCollection();
            CodeStatementCollection afterInvokeColl = new CodeStatementCollection();

            for (int i = 0; i < parameters.Length; i++)
            {
                ParameterInfo pi = parameters[i];
                CodeParameterDeclarationExpression mP = mParameters[i];
                bool isProxyType = IsTypeMappedToProxy(pi.ParameterType);
                CodeTypeReference underlyingParameterType = GetVisibleTypeReference(pi.ParameterType);

                if (mP.Direction == FieldDirection.In || mP.Direction == FieldDirection.Ref)
                {
                    if (isProxyType)
                    {
                        beforeInvokeColl.Add(new CodeConditionStatement(
                            new CodeBinaryOperatorExpression(GetVar(mP.Name), CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null)),
                            new CodeAssignStatement(GetIndexerRef("objs", new CodePrimitiveExpression(i)),
                            GetPropRef(mP.Name, UNDERLYING_INSTANCE_PROPNAME)
                            ))
                            );
                    }
                    else
                    {
                        beforeInvokeColl.Add(new CodeAssignStatement
                            (GetIndexerRef("objs", new CodePrimitiveExpression(i)), GetVar(mP.Name)
                            ));
                    }
                }

                if (mP.Direction == FieldDirection.Out || mP.Direction == FieldDirection.Ref)
                {
                    if (isProxyType)
                    {
                        afterInvokeColl.Add(new CodeAssignStatement(
                            GetVar(mP.Name),
                            GetStaticMethodInvoke(mP.Type, FROMINSTANCE_METHODNAME,
                            new CodeCastExpression(underlyingParameterType, GetIndexerRef("objs", new CodePrimitiveExpression(i))
                        ))));
                    }
                    else
                    {
                        afterInvokeColl.Add(new CodeAssignStatement
                             (GetVar(mP.Name), new CodeCastExpression(mP.Type, GetIndexerRef("objs", new CodePrimitiveExpression(i)))
                            ));
                    }
                }
            }

            coll.AddRange(beforeInvokeColl);

            if (isConstructor)
            {
                coll.Add(
                        DeclareVar("ret", typeof(object),
                        GetMethodInvoke(staticVarName, "Invoke", GetVar("objs"))
                        )
                    );
            }
            else
            {
                CodeExpression callObj = isStatic ? (CodeExpression)new CodePrimitiveExpression(null) : (CodeExpression)GetThisFieldRef(UNDERLYING_INSTANCE_VARNAME);
                coll.Add(
                        DeclareVar("ret", typeof(object),
                        GetMethodInvoke(staticVarName, "Invoke", callObj, GetVar("objs"))
                        )
                    );
            }

            coll.AddRange(afterInvokeColl);

            if (GetRealType(returnType) != typeof(void))
            {
                if (IsTypeMappedToProxy(returnType))
                {
                    coll.Add(new CodeMethodReturnStatement(
                                GetStaticMethodInvoke (GetVisibleOrProxyTypeReference(returnType), FROMINSTANCE_METHODNAME,
                                new CodeCastExpression(GetVisibleTypeReference(returnType), GetVar("ret"))
                            )));
                }
                else
                {
                    coll.Add(new CodeMethodReturnStatement(
                                new CodeCastExpression(GetVisibleTypeReference(returnType), GetVar("ret"))
                            ));
                }
            }
            return coll;
        }

        private CodeMethodInvokeExpression GetMethodInvoke(string varName, string methodName, params CodeExpression[] parameters)
        {
            return new CodeMethodInvokeExpression(
                    new CodeMethodReferenceExpression (new CodeVariableReferenceExpression(varName), methodName),
                    parameters
                );
        }

        private CodeMethodInvokeExpression GetStaticMethodInvoke(CodeTypeReference type, string methodName, params CodeExpression[] parameters)
        {
            return new CodeMethodInvokeExpression(
                    new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(type), methodName),
                    parameters
                );
        }

        private CodeVariableDeclarationStatement DeclareVar(string varName, Type type, CodeExpression initExpression)
        { 
            return new CodeVariableDeclarationStatement(type, varName, initExpression);
        }

        private CodeExpression GetThisOrNullRef(bool isStatic)
        {
            if (!isStatic) return new CodeThisReferenceExpression();
            return new CodePrimitiveExpression(null);
        }

        private CodeVariableReferenceExpression GetVar(string varName)
        {
            return new CodeVariableReferenceExpression(varName);
        }

        private CodeFieldReferenceExpression GetStatFieldRef(string typeName, string fieldName)
        {
            return new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeName), fieldName);
        }

        private CodeFieldReferenceExpression GetFieldRef(string varName, string fieldName)
        {
            return new CodeFieldReferenceExpression(new CodeVariableReferenceExpression(varName), fieldName);
        }

        private CodeFieldReferenceExpression GetThisFieldRef(string fieldName)
        {
            return new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName);
        }

        private CodePropertyReferenceExpression GetPropRef(string varName, string propName)
        {
            return new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(varName), propName);
        }

        private CodeIndexerExpression GetIndexerRef(string varName, params CodeExpression[] indices)
        {
            return new CodeIndexerExpression(new CodeVariableReferenceExpression(varName), indices);
        }

        private bool IsDestructor(MethodInfo mi)
        {
            return mi.Name.Equals("Finalize");
        }

        private bool IsPropertyMethodInfo(MethodInfo mi)
        {
            return ((mi.Name.StartsWith("get_") || mi.Name.StartsWith("set_")) && (mi.Attributes & MethodAttributes.SpecialName) == MethodAttributes.SpecialName);
        }

        private bool IsMethodInterfaceExplicit(MethodInfo mi)
        {
            // probably not the best way to test that
            return mi.Name.IndexOf(".") > 0;
        }

        private Type GetRealType(Type type)
        {
            // gets the real type e.g. => from System.Object& - System.Object
            return type.IsByRef ? type.GetElementType() : type;
        }

        private bool IsTypeMappedToProxy(Type type)
        {
            Type realType = GetRealType(type);
            return this.inputTypes.ContainsKey(realType);
        }

        private CodeTypeReference GetVisibleTypeReference(Type type)
        {
            Type realType = GetRealType(type);
            if (!realType.IsVisible) return new CodeTypeReference(typeof(object));
            return new CodeTypeReference(realType);
        }

        private CodeTypeReference GetVisibleOrProxyTypeReference(Type type)
        {
            Type realType = GetRealType(type);
            if (this.inputTypes.ContainsKey(realType))
            {
                return new CodeTypeReference(this.inputTypes[realType].TypeDeclaration.Name);
            }
            if (!realType.IsVisible) return new CodeTypeReference(typeof(object));
            return new CodeTypeReference(realType);
        }

        private CodeMemberMethod AddMethodFromMethodInfo(CodeTypeDeclaration cl, MethodInfo mi)
        {
            CodeMemberMethod m = AddMethodFromParameterInfos(cl, mi.Name, mi.GetParameters());
            if (mi.IsStatic) m.Attributes |= MemberAttributes.Static;
            m.ReturnType = GetVisibleOrProxyTypeReference(mi.ReturnType);
            return m;
        }

        private CodeMemberMethod AddMethodFromParameterInfos(CodeTypeDeclaration cl, string methodName, ParameterInfo[] parameters)
        {
            CodeMemberMethod m = this.GetMethodFromParameterInfos(methodName, parameters);
            cl.Members.Add(m);
            return m;
        }

        private CodeMemberMethod GetMethodFromParameterInfos(string methodName, ParameterInfo[] parameters)
        {
            CodeMemberMethod m = new CodeMemberMethod();
            m.Name = methodName;
            m.Attributes = MemberAttributes.Public | MemberAttributes.Final;

            foreach (ParameterInfo pi in parameters)
            {
                CodeTypeReference parameterType = GetVisibleOrProxyTypeReference(pi.ParameterType);
                CodeParameterDeclarationExpression par = new CodeParameterDeclarationExpression(parameterType, pi.Name);

                if (pi.IsOut) par.Direction = FieldDirection.Out;
                else if (pi.ParameterType.IsByRef) par.Direction = FieldDirection.Ref;
                else par.Direction = FieldDirection.In;

                m.Parameters.Add(par);
            }
            return m;
        }

        private CodeMemberField AddField(CodeTypeDeclaration cl, Type type, string name)
        {
            CodeMemberField f = new CodeMemberField(type, name);
            f.Attributes = MemberAttributes.Private;
            cl.Members.Add(f); return f;
        }

        private CodeMemberField AddStaticField(CodeTypeDeclaration cl, Type type, string name)
        {
            CodeMemberField f = AddField(cl, type, name);
            f.InitExpression = new CodePrimitiveExpression(null);
            f.Attributes |= MemberAttributes.Static; return f;
        }

        private CodeMemberProperty AddProperty(CodeTypeDeclaration cl, Type type, string name, string fieldName)
        {
            CodeMemberProperty p = new CodeMemberProperty();
            p.Name = name;
            p.Type = new CodeTypeReference(type);
            p.Attributes = MemberAttributes.Public | MemberAttributes.Final;
            p.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));
            p.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodePropertySetValueReferenceExpression()));
            cl.Members.Add(p);
            return p;
        }

        #endregion

        #region generate code

        private void SaveFile()
        {
            using (FileStream fs = new FileStream(this.fileName, FileMode.Create, FileAccess.Write))
            {
                using (StreamWriter sw = new StreamWriter(fs))
                {
                    string s = GenerateCode(this._unit);
                    sw.Write (s);
                    sw.Flush();
                }
            }
        }

        private string GenerateCode(CodeCompileUnit unit)
        {
            CSharpCodeProvider provider = new CSharpCodeProvider();
            StringBuilder sb = new StringBuilder();
            using (StringWriter sw = new StringWriter(sb))
            {
                using (IndentedTextWriter writer = new IndentedTextWriter(sw))
                {
                    CodeGeneratorOptions options = new CodeGeneratorOptions();
                    options.BlankLinesBetweenMembers = true;
                    options.ElseOnClosing = false;
                    options.VerbatimOrder = false;
                    provider.GenerateCodeFromCompileUnit(unit, writer, options);
                }
            }
            return sb.ToString();
        }

        #endregion

        #region dummies

        private static object FromUnderlyingInstance(object instance) { return null; }
        private static MethodInfo FromUnderlyingInstance_MI = typeof(ProxyGenerator).GetMethod("FromUnderlyingInstance", BindingFlags.Static | BindingFlags.NonPublic);

        #endregion
    }
}
